Skip to content

FDI fibre model, iEMG units, ported fixes, sEMG dispersion and calibration tooling - #41

Closed
DRohlf wants to merge 88 commits into
fix/semg-farina2004from
calib/fdi-dd-main
Closed

DRohlf wants to merge 88 commits into
fix/semg-farina2004from
calib/fdi-dd-main

Conversation

@DRohlf

@DRohlf DRohlf commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Second of two stacked PRs. Base is fix/semg-farina2004 (the first PR); retarget to main once that merges, since GitHub only closes issues on merges into the default branch.

Closes #30, closes #31, closes #33, closes #34, closes #35, closes #36, closes #37, closes #38, closes #39.

Fixes

Models

Tooling and docs

Evidence. Old-vs-new comparison on the FDI benchmark (same muscle, NEURON spike trains at 25 % MVC, Delsys 5-pin sensors): v0.13.0 28 mV / 455 Hz → defaults after the fixes 0.25 mV / 235 Hz → with Muscle fibres, dispersion and λ 0.63: 0.19–0.23 mV / 160–165 Hz (Botelho et al. 2019: 0.21 mV, ~165 Hz). Decision record: docs/pr/2026-11-01-upstream-changes.md; design notes in docs/superpowers/specs/.

Tests. Full suite: 515 passed, 2 skipped (587903614); the review fixes after it (0ec4f1e8e, per-unit CV input types) pass their tests.

Upgrade notes. See CHANGELOG.md (Unreleased): iEMG amplitudes and fibre CVs change; Gfluctdv pools become noisy; Muscle._generate_fiber_properties() needs assign_mfs2mns() first.

DRohlf added 30 commits November 1, 2026 23:55
…g onto main

The descending-drive optimiser, its recruitment-curve and rate-target helpers
(ramp_analysis, fr_optimization_targets, parallel_budget) and fdi_spikes_force.py
come from the fresh-trunk line, which predates the ForceModel fix (e794774).
On main the force mapping uses the corrected Fuglevand force. Thresholds come
from RecruitmentThresholds directly (the muscle-profile library is not on main).
Launchers for the 120-MU FDI calibrations (recruitment ending at 70 % and 52 %
excitation, De Luca 1982 targets) write *_main results.
Gfluctdv.mod multiplies both fluctuating conductances by GLOBAL multex/multin,
which default to 0, so AlphaMN.insert_Gfluctdv() added a silent mechanism.
Set both to 1 on insert. Ported from the fresh-trunk line (bef5cbe79,
2026-04-17), which never reached main.
… fibre layouts

Fibre CV used a slope of 5.5 (m/s)/mm with diameters in mm, giving ~1.1 m/s
before the 2 m/s clip, so every fibre had exactly 2.0 m/s. Use CV = 0.95 m/s
+ 0.05 m/s per um (Nandedkar & Stalberg 1983), shared with
SurfaceFiberDispersion through CV_INTERCEPT__M_S / CV_PER_DIAMETER__M_S_PER_MM.
Affects intramuscular EMG; surface EMG uses the mean CV.

voronoi_pi1e5.csv covers a square, so only ~313 k points form a full disc.
Larger fibre counts got a corner-only outer ring and, above 399 499, a silent
cap. Those counts now use a scrambled Halton sequence in the disc (seeded from
MyoGen's RNG) with a warning; smaller counts are unchanged.

Fresh-trunk fixed both differently (be9cdfe35, a82923b2b); see
docs/pr/2026-11-01-upstream-changes.md for why its numbers were not used.
…itment ceiling term

The largest FDI units were recruited late (0.62-1.0 excitation for designed
0.38-0.52) and out of order. Every motor neuron drew a binomial number of DD
inputs (38-66) of one weight, and large dendrites shunt more, so one input
delivered ~1.9x less current to a large soma than to a small one.

- Each motor neuron gets round(p * N_DD) inputs.
- Input weight scaled by the neuron's DC dendrite-to-soma transfer
  (h.Impedance), so each input delivers the same current to a clamped soma
  at low drive. Modelling assumption: equal effective synaptic current per
  input (equal-share case of Heckman & Binder 1991).
- Each trial ramps excitation to 1 and penalises the distance between the
  last unit's recruitment excitation and --max-recruitment.
- Parameter files record the wiring in a `network` block; files without it
  are refused by run_dd_ramp / run_dd_constant.
docs/literature.md lists the sources behind the surface-EMG defaults,
fibre-dispersion options and descending-drive calibration: a parameter
index, per-source verification status (full text, figure, abstract,
secondary) and use (adopted, context, rejected), links to the code and
tests that use each source, and a corrections log. Linked from the docs
nav, the CHANGELOG and the PR notes.
The ceiling term compared the excitation at which the last unit is
recruited with --max-recruitment. That target was derived from one force
curve; the new DD wiring moved the curve, so excitation 0.41 meant ~70 %
MVC instead of ~55 %.

--last-recruit-mvc PCT now runs an MVC ramp (0.5 s rest, excitation 0 -> 1
at 0.1/s, 3 s hold), computes force with ForceModel, and penalises the
force (% MVC) at the last sustained recruitment against PCT. Off by
default. --max-recruitment only sets the rate-coding targets.
fdi_spikes_force.py reuses the optimiser's MVC ramp (output unchanged on
the _wired calibration) and reports the last-recruitment force.
…ing, high-force rate checks

ForceModel(force_frequency_shape=(a, c)) generalises the Fuglevand saturation; default unchanged.
HUMAN_UNIT_FORCE_FREQUENCY is fitted to Macefield 1996, Fuglevand 1999 and McNulty 2000.
Optimiser: --force-frequency, --ceiling-rank, --high-force-rates (De Luca 1982 40/80 % MVC rates).
DRohlf added 21 commits November 3, 2026 00:25
gen_arm generates the Muscle fibre properties (global generator state restored)
when an arm sets fiber_properties="muscle", records their hash, and takes an
_iap_kernel_length__mm key. Arms delsys5r_wexp_muscle_lam0.6-1 (kernel 200 mm) and
a kernel-200 control of the lambda 0.7 recruitment-force arm. CHANGELOG, PR doc,
backlog.
Literature register rebuilt (line anchors moved; new delvecchio2018 citation in
muscle_candidate). Per-fibre test that each fibre gets its own Muscle CV and
diameter (fails when the order within a unit is reversed). Spec: report field
diameter_mean__um keeps its existing meaning.
MDF matches ~165 Hz at lambda ~0.72 (0.70 and 0.75 tie within one 2.5 Hz
bin); every lambda 0.6-1 lies inside Botelho's MDF IQR. Corner-centre RMS
at the fit is below Botelho's IQR. Default stays 1.
Pages 464-471 belong to the normal concentric-needle paper; the
pathological-situations paper is 112:869-878. Checked against Zotero.
…unit means

Within-unit diameter log-SD 0.08 / 0.13 (type I / II) from Martin et al.
1988; every fibre's CV on one affine map of the CV-diameter line, SD over
unit means, mean over fibres; no extra CV scatter. within_unit_fiber_
diameter_log_sd=None restores the previous fibres bit-identically.
motor_unit_conduction_velocities__m_per_s sets each unit's mean fibre CV
(e.g. from recruitment force through the Del Vecchio band means); within a
unit CV follows diameter on the Nandedkar-Stalberg slope. Without it, the
4.32 +/- 0.71 m/s holds over unit means. Benchmark key
_muscle_unit_cv_file and musclert arms for the lambda refit.
…n when >5 % of fibres hit 6 m/s

cv_from_recruitment_force adds N(0, 0.39 m/s) per unit (sqrt(1 - R^2 0.70) x
0.71 m/s, Del Vecchio 2018), seeded from myogen's generator or rng; pass
residual_sd__m_s=0 for the line alone. Muscle warns when more than 5 % of
fibres are clipped to 6 m/s and points to per-unit CVs. Benchmark arms for
the old-vs-new comparison and the scatter check.
…unit; document the mean/SD under the 6 m/s bound

cv_from_recruitment_force returns a plain array, which beartype rejected.
Review findings; minors recorded in the backlog. Register: Del Vecchio entry
updated (over unit means, residual scatter).
@DRohlf

DRohlf commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator Author

Replaced by #43 (ported fixes), #44 (tooling) and #45 (fibre model), stacked on #40, after review. Closing in favour of those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant